using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace TBit.Compile {
    public class ASMCompileError : Exception {
        public ASMCompileError(string str):base(str){}
    }

    public class ASMCompile {
        /*
            指令 XXX
            变量 @[xxxx]
            数据 $[xxxx]
            寄存器 #[R1-4]
        */
        public enum TokenType {
            Instruction,
            Variable,
            Register,
            Data,
            Other,
        }
        public class Token {
            public TokenType type;
            public string str;
        }

        public static string[] instruction_strings = new string[]{
            "mov","dw","dd","add","sub","mul","div",
            "jmp","cjmp","ofsjmp","ofscjmp","gr","le",
            "eq","neq","call","push","pop","popr","df",
            "ret","and","or","not"
        };

        public static bool TokenISRVD(Token t) {
            return t.type == TokenType.Data || t.type == TokenType.Register || t.type == TokenType.Variable;
        }

        public static InstructionArgData TokenTOInstructionArgData(Token tk) {
            InstructionArgData arg = new InstructionArgData();
            switch(tk.type)
            {
                case TokenType.Register : {
                    arg.type = InstructionArgDataType.Register;
                    switch(tk.str)
                    {
                        case "R1" : arg.register = RegisterIndex.R1;break;
                        case "R2" : arg.register = RegisterIndex.R2;break;
                        case "R3" : arg.register = RegisterIndex.R3;break;
                        case "R4" : arg.register = RegisterIndex.R4;break;
                        case "RET" : arg.register = RegisterIndex.RET;break;
                        case "CP" : arg.register = RegisterIndex.CP;break;
                    }
                }break;
                case TokenType.Data : {
                    arg.type = InstructionArgDataType.Data;
                    Data data = new Data();
                    arg.data = data;
                    if(Regex.IsMatch(tk.str,@"\d+(\.\d*)?"))
                    {
                        data.type = DataType.Number;
                        data.number_data = Double.Parse(tk.str);
                    }
                    else if(tk.str == "true" || tk.str == "false")
                    {
                        data.type = DataType.Boolean;
                        data.boolean_data = tk.str == "true"?true:false;
                    }
                    else
                    {
                        data.type = DataType.String;
                        if(tk.str.Length > 2)
                        {
                            data.string_data = tk.str.Substring(1,tk.str.Length - 2);
                        }
                    }
                }break;
                case TokenType.Variable : {
                    arg.type = InstructionArgDataType.Variable;
                    arg.variable = tk.str;
                }break;
            }
            return arg;
        }

        /*
            指令类型

            A : OPCODE DVR R
            B : OPCODE V R
            C : OPCODE R R R
            D : OPCODE R
        */
        public static void  StructureInstruction_Read_R(List<Token> tokens,Instruction inst,ref int i) {
            Token next_tk = tokens[i];
            if(next_tk.type != TokenType.Register) throw new ASMCompileError("指令格式错误R");
            var arg2 = TokenTOInstructionArgData(next_tk);
            inst.args.Add(arg2);
        }

        public static void  StructureInstruction_Read_V(List<Token> tokens,Instruction inst,ref int i) {
            Token next_tk = tokens[i];
            if(next_tk.type != TokenType.Variable) throw new ASMCompileError("指令格式错误V");
            var arg2 = TokenTOInstructionArgData(next_tk);
            inst.args.Add(arg2);
        }

        public static void  StructureInstruction_Read_DVR(List<Token> tokens,Instruction inst,ref int i) {
            Token next_tk = tokens[i];
            if(!TokenISRVD(next_tk)) throw new ASMCompileError("指令格式错误DVR");
            var arg1 = TokenTOInstructionArgData(next_tk);
            inst.args.Add(arg1);
        }

        public static void StructureInstruction_Read_D(List<Token> tokens,Instruction inst,ref int i) {
            Token next_tk = tokens[i];
            if(next_tk.type != TokenType.Data) throw new ASMCompileError("指令格式错误D");
            var arg1 = TokenTOInstructionArgData(next_tk);
            inst.args.Add(arg1);
        }

        public static void StructureInstruction_Next(ref int i) {
            i++;
        }
        public static int StructureInstruction_OPCODE_DVR_R(Opcode opcode,List<Token> tokens,VM vm,int i) {
            Instruction inst = new Instruction();
            inst.opcode = opcode;
            StructureInstruction_Next(ref i);
            StructureInstruction_Read_DVR(tokens,inst,ref i);
            StructureInstruction_Next(ref i);
            StructureInstruction_Read_R(tokens,inst,ref i);
            vm.PushInstructon(inst);
            return i;
        }

        public static int StructureInstruction_OPCODE_V_R(Opcode opcode,List<Token> tokens,VM vm,int i) {
            Instruction inst = new Instruction();
            inst.opcode = opcode;
            StructureInstruction_Next(ref i);
            StructureInstruction_Read_V(tokens,inst,ref i);
            StructureInstruction_Next(ref i);
            StructureInstruction_Read_R(tokens,inst,ref i);
            vm.PushInstructon(inst);
            return i;
        }
        public static int StructureInstruction_OPCODE_V_D(Opcode opcode,List<Token> tokens,VM vm,int i) {
            Instruction inst = new Instruction();
            inst.opcode = opcode;
            StructureInstruction_Next(ref i);
            StructureInstruction_Read_V(tokens,inst,ref i);
            StructureInstruction_Next(ref i);
            StructureInstruction_Read_D(tokens,inst,ref i);
            vm.PushInstructon(inst);
            return i;
        }

        public static int StructureInstruction_OPCODE_V(Opcode opcode,List<Token> tokens,VM vm,int i) {
            Instruction inst = new Instruction();
            inst.opcode = opcode;
            StructureInstruction_Next(ref i);
            StructureInstruction_Read_V(tokens,inst,ref i);
            vm.PushInstructon(inst);
            return i;
        }


        public static int StructureInstruction_OPCODE_R_R_R(Opcode opcode,List<Token> tokens,VM vm,int i) {
            Instruction inst = new Instruction();
            inst.opcode = opcode;
            StructureInstruction_Next(ref i);
            StructureInstruction_Read_R(tokens,inst,ref i);
            StructureInstruction_Next(ref i);
            StructureInstruction_Read_R(tokens,inst,ref i);
            StructureInstruction_Next(ref i);
            StructureInstruction_Read_R(tokens,inst,ref i);
            vm.PushInstructon(inst);
            return i;
        }

        public static int StructureInstruction_OPCODE_R_R(Opcode opcode,List<Token> tokens,VM vm,int i) {
            Instruction inst = new Instruction();
            inst.opcode = opcode;
            StructureInstruction_Next(ref i);
            StructureInstruction_Read_R(tokens,inst,ref i);
            StructureInstruction_Next(ref i);
            StructureInstruction_Read_R(tokens,inst,ref i);
            vm.PushInstructon(inst);
            return i;
        }

        public static int StructureInstruction_OPCODE_R(Opcode opcode,List<Token> tokens,VM vm,int i) {
            Instruction inst = new Instruction();
            inst.opcode = opcode;
            StructureInstruction_Next(ref i);
            StructureInstruction_Read_R(tokens,inst,ref i);
            vm.PushInstructon(inst);
            return i;
        }

        public static int StructureInstruction_OPCODE(Opcode opcode,List<Token> tokens,VM vm,int i) {
            Instruction inst = new Instruction();
            inst.opcode = opcode;
            vm.PushInstructon(inst);
            return i;
        }

        public static int StructureInstruction_OPCODE_V_DVR(Opcode opcode,List<Token> tokens,VM vm,int i) {
            Instruction inst = new Instruction();
            inst.opcode = opcode;
            StructureInstruction_Next(ref i);
            StructureInstruction_Read_V(tokens,inst,ref i);
            StructureInstruction_Next(ref i);
            StructureInstruction_Read_DVR(tokens,inst,ref i);
            vm.PushInstructon(inst);
            return i;
        }

        public static int StructureInstruction_OPCODE_DVR(Opcode opcode,List<Token> tokens,VM vm,int i) {
            Instruction inst = new Instruction();
            inst.opcode = opcode;
            StructureInstruction_Next(ref i);
            StructureInstruction_Read_DVR(tokens,inst,ref i);
            vm.PushInstructon(inst);
            return i;
        }

        public Opcode GetOpcode(string word) {
            word = word.ToUpper();
            switch(word)
            {
                case "MOV" : return Opcode.MOV;
                case "DW" : return Opcode.DW;
                case "DD" : return Opcode.DD;
                case "ADD" : return Opcode.ADD;
                case "SUB" : return Opcode.SUB;
                case "MUL" : return Opcode.MUL;
                case "DIV" : return Opcode.DIV;
                case "JMP" : return Opcode.JMP;
                case "OFSJMP" : return Opcode.OFSJMP;
                case "CJMP" : return Opcode.CJMP;
                case "OFSCJMP" : return Opcode.OFSCJMP;
                case "EQ" : return Opcode.EQ;
                case "NEQ" : return Opcode.NEQ;
                case "LE" : return Opcode. LE;
                case "GR" : return Opcode.GR;
                case "DF" : return Opcode.DF;
                case "PUSH" : return Opcode.PUSH;
                case "POP" : return Opcode.POP;
                case "POPR" : return Opcode.POPR;
                case "CALL" : return Opcode.CALL;
                case "RET" : return Opcode.RET;
                case "AND" : return Opcode.AND;
                case "OR" : return Opcode.OR;
                case "NOT" : return Opcode.NEQ;
            }
            return Opcode.MOV;
        }


        public static Dictionary<Opcode,Func<List<Token>,VM,int,int>> InstructionCompileDefines = new Dictionary<Opcode,Func<List<Token>,VM,int,int>>();

        public bool IsInstruction(string word) {
            foreach(string str in instruction_strings)
            {
                if(word.ToLower() == str)
                {
                    return true;
                }
            }
            return false;
        }

        string reg_regex = @"#(\[(R1|R2|R3|R4|RET|CP))\]";
        string data_regex = @"\$(\[((\d+(\.\d*)?)|(""(.*)"")|(true|false))\])";
        string variable_regex = @"\@\[([a-zA-Z_])*([a-zA-Z0-9_]|_)+\]";

        public TokenType GetWordType(string word) {
            if(IsInstruction(word)) return TokenType.Instruction;
            if(Regex.IsMatch(word,reg_regex)) return TokenType.Register;
            if(Regex.IsMatch(word,data_regex)) return TokenType.Data;
            if(Regex.IsMatch(word,variable_regex)) return TokenType.Variable;
            return TokenType.Other;
        }   

        public static bool IsLetter(char c) {
            return (c >= 65 && c <= 90) ||  (c >= 97 && c <= 122);
        }

        public static bool IsHeadSymbol(char c) {
            return c == '$' || c == '#' || c == '@'; 
        }

        public static bool IsNumber(char c) {
            return c >= 48 && c <= 57;
        }

        public static bool IsSeparateSymbol(char c) {
            return c == ',';
        }

        public string ReadWord(ref int i,ref string code) {
            string str = "" + code[i];
            i++;
            for(;i < code.Length;)
            {
                if(code[i] == '"')
                {
                    str+=code[i];
                    i++;
                    for(;i < code.Length;)
                    {
                        if(code[i] == '"')
                        {
                            str+=code[i];
                            i++;
                            break;
                        }
                        str+=code[i];
                        i++;
                    }
                }
                else
                {
                    if(code[i] >= 0 && code[i] <= 32
                    || code[i] == ','
                    ) break;
                    str += code[i];
                    i++;
                }
            }
            return str;
        }

        public void Init() {
            InstructionCompileDefines[Opcode.MOV] = Instrcutions.Mov.Compile;
            InstructionCompileDefines[Opcode.DD]  = Instrcutions.DD.Compile;
            InstructionCompileDefines[Opcode.DW]  = Instrcutions.Dw.Compile;
            InstructionCompileDefines[Opcode.ADD] = Instrcutions.Add.Compile;
            InstructionCompileDefines[Opcode.SUB] = Instrcutions.Sub.Compile;
            InstructionCompileDefines[Opcode.MUL] = Instrcutions.Mul.Compile;
            InstructionCompileDefines[Opcode.DIV] = Instrcutions.Div.Compile;
            InstructionCompileDefines[Opcode.GR] = Instrcutions.Gr.Compile;
            InstructionCompileDefines[Opcode.LE] = Instrcutions.Le.Compile;
            InstructionCompileDefines[Opcode.EQ] = Instrcutions.Eq.Compile;
            InstructionCompileDefines[Opcode.NEQ] = Instrcutions.Neq.Compile;
            InstructionCompileDefines[Opcode.JMP] = Instrcutions.Jmp.Compile;
            InstructionCompileDefines[Opcode.CJMP] = Instrcutions.Cjmp.Compile;
            InstructionCompileDefines[Opcode.OFSJMP] = Instrcutions.Ofsjmp.Compile;
            InstructionCompileDefines[Opcode.OFSCJMP] = Instrcutions.Ofscjmp.Compile;
            InstructionCompileDefines[Opcode.PUSH] = Instrcutions.Push.Compile;
            InstructionCompileDefines[Opcode.POP] = Instrcutions.Pop.Compile;
            InstructionCompileDefines[Opcode.POPR] = Instrcutions.Popr.Compile;
            InstructionCompileDefines[Opcode.CALL] = Instrcutions.Call.Compile;
            InstructionCompileDefines[Opcode.DF] = Instrcutions.DF.Compile;
            InstructionCompileDefines[Opcode.RET] = Instrcutions.Ret.Compile;
            InstructionCompileDefines[Opcode.AND] = Instrcutions.And.Compile;
            InstructionCompileDefines[Opcode.OR] = Instrcutions.Or.Compile;
            InstructionCompileDefines[Opcode.NOT] = Instrcutions.Not.Compile;
        }

        public List<Token> ProcessWord(string code) {
            List<Token> words = new List<Token>();

            for(int i = 0;i< code.Length;)
            {
                char c = code[i];
                if(IsLetter(c) || IsHeadSymbol(c))
                {
                    string word = ReadWord(ref i,ref code);
                    TokenType type = GetWordType(word);
                    if(type != TokenType.Other && type != TokenType.Instruction)
                    {
                        word = Regex.Match(word,@"\[[^}]+\]").Value;
                        word = word.Substring(1,word.Length - 2);
                    }

                    Token tk = new Token();
                    tk.type = type;
                    tk.str = word;
                    words.Add(tk);
                }
                i++;
            }

            return words;
        }

        public void Compile(List<Token> tokens,VM vm) {
            for(int i = 0;i< tokens.Count;i++)
            {
                Token tk = tokens[i];
                if(tk.type == TokenType.Instruction)
                {
                    var opcode = GetOpcode(tk.str);
                    if(InstructionCompileDefines.ContainsKey(opcode))
                    {
                        var func = InstructionCompileDefines[opcode];
                        i = func(tokens,vm,i);
                    }
                }
            }
        }
    }
}